#include <errno.h>
#include <signal.h>
#include <ucontext.h>
#include <stdio.h>
#include <sys/time.h>
#include <fcntl.h>

#include "crossbit/IA32Debugger.h"
#include "crossbit/IA32Inst.h"
#include "crossbit/IA32BasicBlockBuilder.h"
#include "crossbit/SimpleTCache.h"
#include "crossbit/X86Translator.h"
#include "crossbit/IA32Syscall.h"
#include "crossbit/IA32BlockContext.h"
#include "crossbit/BlockContextManager.h"
#include "crossbit/IA32BlockContextManager.h"
#include "crossbit/X86TraceManager.h"

using namespace crossbit;

IA322x86Debugger* IA322x86Debugger::pInstance_ = NULL;


void IA322x86Debugger::process_exception( int signum, char* addr )
{
    if( signum == SIGSEGV )
    {
	//todo: verify the address here
	printf( "Segmentation fault: at 0x%x\n", addr);
    }
    else
    {
	//the "int 3" instruction is 1 byte long
	std::map< char*, BreakPoint*>::iterator it = rmap.find( addr - 1);
	
	if( it != rmap.end() )
	{
	    BreakPoint* pBreak = it->second;
            char buf[64];
	    
            curPC_ = pBreak->vaddr;
           
	    //Using format ouput 
	    printf("Breakpoint at address 0x%08x\n", pBreak->vaddr);
	    printf("0x%08x:\t", pBreak->vaddr);
	    
	    if( disas( &(((IA32BreakPoint*)pBreak)->inst), buf, curPC_) )
	    {
		std::cout << buf << std::endl;
	    }
	}
	
	//Get current pc's instruction word
	std::map<MemAddr, BreakPoint*>::iterator mit = vmap.find(curPC_);

	if( mit != vmap.end() )
	{
	    pCurInst_ = &(((IA32BreakPoint*)(mit->second))->inst);
	}
	else
	{
	    pCurInst_ = (IA32OpsFmt*)(pImage_->map(curPC_));
	}
    }
}

void IA322x86Debugger::console()
{
    prompt();
    
    while( true )
    {
	char command[64];
	int  i = 0;
	char c;
	char* cmd;
	char* cmdend;
	char buf[128];

	int savedStdin = dup(0);
	int newfd = open("/dev/tty", O_RDONLY);

	close(0);
	dup(newfd);
	
	while( (c = getchar()) != '\n' )
	    command[i++] = c;	
	command[i] = '\0';

	close(0);
	dup(savedStdin);
	close(savedStdin);
	close(newfd);

	cmd = command;
	while( *cmd ==' ' )
	    cmd ++;
	
	cmdend = cmd;
	while( *cmdend != ' ' && *cmdend != '\0' )
	    cmdend ++;
	*cmdend = '\0';

	if( strcmp(cmd, "b") == 0 || strcmp(cmd, "break") == 0 )
	{
	    //todo: verify the command format first
	    MemAddr addr;
	    char* saddr = cmdend + 1;
	    sscanf(saddr, "%x", &addr);

	    //todo: verify the address first
	    add_breakpoint( addr );
	    pCache_->eraseBlockContains(addr);
	}
	else if( strcmp(cmd, "clear") == 0 )
	{
	    MemAddr addr;
	    char* saddr = cmdend + 1;
	    sscanf(saddr, "%x", &addr);

	    clear_breakpoint( addr );
	    pCache_->eraseBlockContains(addr);
	}
	else if( strcmp(cmd, "s") == 0 || strcmp(cmd, "step") == 0 )
	{
	    pTranslator_->setFirstTime();
	    saveBlockContext( curPC_, curPC_);
	    
	    step();

	    display_current_instruction();
	}
	else if( strcmp(cmd, "") == 0 || strcmp(cmd, "c") == 0 || strcmp(cmd, "cont") == 0 )
	{
	    pTranslator_->setFirstTime();
	    saveBlockContext( curPC_, curPC_ );
	    step();
	    
	    pTranslator_->setFirstTime();
	    go();	
	}
	else if( strcmp(cmd, "r") == 0 || strcmp(cmd, "regs") == 0 )
	{
	    display_regs();
	}
	else if( strcmp(cmd, "iregs") == 0 )
	{
	    display_iregs();
	}
	else if( strcmp(cmd, "fregs") == 0 )
	{
	    display_fregs();
	}
	else if( strcmp(cmd, "x") == 0 )
	{
	    //todo: verify the command format here
	    char* fmt = cmdend;
	    char* saddr;
	    MemAddr addr;
	    char buf[16];
	    int j = 0;
	    int num;
	    int c;

	    while( *fmt != '/' )
		fmt ++;
	   
	    fmt ++;
	    while( *fmt >= '0' && *fmt <= '9' )
		buf[j++] = *(fmt++);
	    buf[j] = '\0';

	    num = atoi(buf);
	    *(fmt+1) ='\0';
	    
	    saddr = fmt + 2;
	    sscanf( saddr, "%x", &addr );

	    display_memory( addr, fmt, num );
	}
	else if( strcmp(cmd, "back") == 0 )
	{
	    IA322x86Debugger::getInstance()->do_backTracking();
	}
	else if( strcmp(cmd, "q") == 0 )
	{
	    exit(0);
	}
	else if( strcmp(cmd, "help") == 0 )
	{
	    IA322x86Debugger::getInstance()->usage();
	}
	else
	{
	    std::cout << "Unknown command: " << cmd << std::endl;
	}	    
	
	prompt();
    }
}

void IA322x86Debugger::saveBlockContext( int enterPC, int lastPC )
{
    /*
    BlockContext* bc = bcManager_.addBlockContext();
    bc->setProcessorState( pState_ );
    bc->setEnterPC( enterPC );
    bc->setLastPC( lastPC );

    */
    //bc->saveStackData( (char*)pImage_->map(pState_->reg(5)) );
}

void IA322x86Debugger::do_backTracking()
{
    int steps = 0;
	
    BlockContext* bc = bcManager_.getLatestBlockContext();
    
    if( bc == NULL )
    {
	std::cout << "No BlockContext is saved, stop Backtracking" << std::endl;
	return;   
    }
    
    if( curPC_ == bc->getEnterPC() )
    {
	bcManager_.removeLatestBlockContext();
	do_backTracking();
	
	return;
    }
    else
    {
	if( curPC_ <= bc->getLastPC() && curPC_ >= bc->getEnterPC() )
	{
	    steps = ( curPC_ - bc->getEnterPC() - 4 )/4;	
	}
	else
	{
	    steps = ( bc->getLastPC() - bc->getEnterPC())/4; 
	}
    }
    
    //restore registers
    *(dynamic_cast<IA32Interpreter::IA32State*>(pState_)) = *( dynamic_cast<IA32Interpreter::IA32State*>(bc->getProcessorState()));
   
    //restore stack data 
    const char* pSavedData = bc->getSavedStackData();
    char* pStack = (char*) pImage_->map(pState_->reg(29));
    for( int j = 0; j < 1024; j ++ )
    {
	pStack[j] = pSavedData[j];
    }

    MemAddr tmpPC = curPC_;
	
    curPC_ = bc->getEnterPC();
    std::map<MemAddr, BreakPoint*>::iterator mit = vmap.find(curPC_);
    if( mit != vmap.end() )
    {
	pCurInst_ = &(((IA32BreakPoint*)(mit->second))->inst);
    }
    else
    {
	pCurInst_ = (IA32OpsFmt*)(pImage_->map(curPC_));
    }

    bcManager_.backOneInstruction( tmpPC );
    
    pTranslator_->setFirstTime();
    for( ; steps > 0; steps -- )
    {
	step(); 
    }

    display_current_instruction();
}

void IA322x86Debugger::step()
{
    std::map<MemAddr, BreakPoint*>::iterator tit = vmap.find(curPC_);
    IA32OpsFmt* inst = NULL;
        
    if( tit != vmap.end() )
    {
	inst = &(((IA32BreakPoint*)(tit->second))->inst);
	*((unsigned int*)pImage_->map(curPC_)) = inst->w;
    }

    pBuilder_->getReady( curPC_ );
    MemAddr n = pInterp_->decode( curPC_, pBuilder_ );
    VBlock* vb = pBuilder_->wrapUp(n);

    if( inst != NULL )
    {
	*((char*)(pImage_->map(curPC_))) = 0xcc;
    }

    TBlock* tblock = pCache_->requestTBlock(vb);
    pCache_->eraseTBlock( tblock );
    pTranslator_->encode(vb, tblock);
    pCache_->submitTBlock(tblock);
    pCache_->eraseTBlock( tblock );

    MemAddr enter = tblock->enterAddr();

    static int count = 0;
    count ++;

    if (count == 2)
    {
	write(10, (char*)enter, 1000);
    }
    CONTEXT_SAVE
	((void (*)())enter)();
    CONTEXT_RESTORE

    TBlock::Exit exit = tblock->getLastExit();
    if( exit.why == TBlock::ExitReason::SYSCALLEXIT)
    {
	pSyscall_->operator()();
    }
    
    curPC_ = exit.next;	
     
    std::map<MemAddr, BreakPoint*>::iterator mit = vmap.find(curPC_);
        
    if( mit != vmap.end() )
    {
	pCurInst_ = &(((IA32BreakPoint*)(mit->second))->inst);
    }
    else
    {
	pCurInst_ = (IA32OpsFmt*)(pImage_->map(curPC_));
    }
}

void IA322x86Debugger::go()
{
    X86TraceManager traceManager;

    TBlock* preBlock = NULL;

    static int flag = 0;
    static int flag1 = 0;
    MemAddr prePC = 0;
    static int count = 0;
    static int count1 = 0;
    while(1)
    {
	TBlock* tblock = pCache_->lookup(curPC_);

	if (curPC_ == 0x804a2d0)
	{
	    // count ++;
	    flag = 1;
	}
	
	// if (curPC_ == 0x804822b)
	// flag = 1;
	/*
	if (curPC_ == 0x805a8f7)
	    flag = 0;
	    */

	/*
	if (count == 2)
	{
	    flag = 1;
	}
	*/

	if (flag == 1)
	{
    	      char buf[32];
    	      //sprintf(buf, "entryAddr: 0x%08x:%08x\n", curPC_, *(int*)pImage_->map(0x3fffb56c));
	      sprintf(buf, "entryAddr: 0x%08x:%08x\n", curPC_, pState_->reg(1));
	      write(15, buf, 32);
	}
	
	//if (count1 == 4)
	// {
	//    flag1 = 1;	
	// }

	/*
	if (*(int*)pImage_->map(0x83839cc) == 0x1880)
	{
	    printf("entryAddr: 0x%08x\n", curPC_);
	    printf("prePC: 0x%08x\n", prePC);
	    flag = 1;
	}
	*/

	// if (flag1 == 1)
	// {
      //		char buf[32];
//		sprintf(buf, "entryAddr: 0x%08x:%08x\n", curPC_, pState_->reg(7));
//		write(15, buf, 32);

	// }
	//    printf("entryAddr: 0x%08x\n", curPC_);

	/*
	if (pState_->reg(7) == 0x1880)
	{
	    printf("entryAddr: 0x%08x\n", curPC_);
	    printf("prePC: 0x%08x\n", prePC);
	}
	*/
	
	/*
	if (flag == 1)
	{
		  	char buf[32];
			//sprintf(buf, "entryAddr: 0x%08x:%08x\n", curPC_, *(int*)pImage_->map(0x82599e4));
			printf("entryAddr: 0x%08x:%08x\n", curPC_, pState_->reg(7));
			//sprintf(buf, "entryAddr: 0x%08x:%08x\n", curPC_, pState_->reg(7));
			//printf("curPC_: 0x%08x\n", curPC_);
			//sprintf(buf, "entryAddr: 0x%08x 0x824d494: 0x%08x\n", curPC_, *(int*)pImage_->map(0x82599e4));
			//write(15, buf, 32);
	}
	*/

	if( tblock == NULL )
	{
	    pBuilder_->getReady(curPC_);
	    MemAddr next = curPC_;
	    
	    while( !pBuilder_->isEnd() )
	    {
		next = pInterp_->decode( next, pBuilder_);
	    }
	    
	    VBlock* vb = pBuilder_->wrapUp(next);
	    tblock = pCache_->requestTBlock(vb);
	    pTranslator_->encode(vb, tblock);
	    pCache_->submitTBlock(tblock);

	    /*
	    if ( curPC_ == 0x8078e58)
		vb->print(std::cout);
		*/
	}
	
	// saveBlockContext( curPC_, tblock->vblock()->nextSPC() - 4 );
	
	MemAddr enter = tblock->enterAddr();
	
	if ( curPC_ == 0x8078e58)
	{
	    write(10, (char*)enter, 2000);
	    exit(1);
	}
	/*
	if( preBlock != NULL )
	{
	    VBlock* vbk = preBlock->vblock();
	    VInst* inst = *vbk->rbegin();

	    if( inst->getOpcode() == OP_BRANCH && inst->getOperand(4).reg == 0 )
	    {
		traceManager.directLinkTo( preBlock, preBlock->getLastExitNo(), tblock);
		//preBlock->linkTo( preBlock->getLastExitNo(), enter );
	    }	
	    else if( inst->getOpcode() == OP_JMP && inst->getOperand(1).reg != 0 )
	    {
		traceManager.indirectLinkTo( preBlock, preBlock->getLastExitNo(), tblock);
	    }
	}
	*/

	CONTEXT_SAVE
	    ((void(*)())enter)();
	CONTEXT_RESTORE
	
	//TBlock::Exit texit = tblock->getLastExit();
	preBlock = X86Translator::getLastBlock(); 
	TBlock::Exit texit = preBlock->getLastExit();

	    
	if( texit.why == TBlock::ExitReason::SYSCALLEXIT)
	{
	    if( pSyscall_->syscode() == SYSCODE_EXIT)
		break;
	    
	    pSyscall_->operator()();
	}
	
	prePC = curPC_;
	curPC_ = texit.next;
    }
}

void IA322x86Debugger::add_breakpoint(MemAddr addr)
{
    // Before using the address, verify it
    // We assume the address is valid here
    
    BreakPoint* bp = new BreakPoint();
    bp->vaddr = addr;
    ((IA32BreakPoint*)bp)->inst.w = *((Word*)(pImage_->map(addr)));
 
    vmap.insert( std::make_pair((MemAddr)addr, bp));

    *((char*)(pImage_->map(addr))) = 0xcc;

    printf("Set a breakpoint at address: %08x\n", addr);
}

void IA322x86Debugger::clear_breakpoint(MemAddr addr)
{
    std::map<MemAddr, BreakPoint*>::iterator it = vmap.find(addr);

    if( it != vmap.end() )
    {
	BreakPoint* bp = it->second;
	*((IA32OpsFmt*)(pImage_->map(addr))) = ((IA32BreakPoint*)bp)->inst;
 
	vmap.erase( it );
	delete bp;

	if( addr == curPC_ )
	    pCurInst_ = (IA32OpsFmt*)(pImage_->map(curPC_));
    }
}

void IA322x86Debugger::display_memory(MemAddr addr, char* fmt, int num)
{
    //todo: verify the address here

    if( strcmp( fmt, "x" ) == 0 )
    {
	for( int i = 0; i < num; i ++ )
	{
	    if( i % 4 == 0 )
	    {
		printf("\n0x%08x: ", addr + i * 4);
	    }

	    printf("%08x  ", *(int*)pImage_->map(addr + i * 4));
	}
    }
    else if( strcmp( fmt, "d" ) == 0 )
    {
	for( int i = 0; i < num; i ++ )
	{
	    if( i % 4 == 0 )
	    {
		printf("\n0x%d: ", addr + i * 4);
	    }

	    printf("%d  ", *(int*)pImage_->map(addr + i  * 4 ));
	}
    }
    else
    {
	printf( "format invalid\n");
    }
}

namespace crossbit
{
    /*
     * Set of breakpoints, index by real address
     */
    extern std::map< char* , BreakPoint* > rmap;

    /*
     * Set of breakpoints, index by virtual address
     */
    extern std::map< MemAddr, BreakPoint* > vmap;

    static void enable_breakpoint()
    {
	sigset_t newmask;

	sigemptyset( &newmask );
	sigaddset( &newmask, SIGTRAP );
	sigaddset( &newmask, SIGSEGV );
	
	if( sigprocmask( SIG_UNBLOCK, &newmask, NULL) < 0 )
	{
	    std::cerr << "Cannot reset signal mask" << std::endl;
	    exit(1);
	}
    }
    
    static void debugger_handler( int signum, siginfo_t* si, void* sc)
    {
	struct ucontext* uc = (struct ucontext*)sc;
	char *p = (char*)((uc->uc_mcontext).gregs)[14];
        
        char buf[64];

	IA322x86Debugger::getInstance()->process_exception( signum, p );

	enable_breakpoint();
	IA322x86Debugger::getInstance()->console();
    }
    
    
    void ia32_debugger_init( ELFMemoryImage* img, TCache* cache, ProcessorState* cpu,
			IA32Interpreter* interp, IA32Syscall* syscall, X86Translator* translator,
			IA32BasicBlockBuilder* builder)
    {

        struct sigaction s;

	s.sa_flags = SA_SIGINFO;
	s.sa_sigaction = debugger_handler;

	if( sigaction( SIGTRAP, &s, (struct sigaction*)NULL))
	{
	    printf("sigaction returned error = %x\n", errno);

	    exit(0);
	}
	if( sigaction( SIGSEGV, &s, (struct sigaction*)NULL) )
	{
	    printf( "sigaction returned error = %x\n", errno);
	    exit(0);
	}

	IA322x86Debugger::getInstance()->init(cpu, img, interp, syscall, translator,
						builder, cache);
	IA322x86Debugger::getInstance()->add_breakpoint(img->entry());
    }

    /************************************************************************/

    int disas( IA32OpsFmt* pInst, char* buf, MemAddr CPC)
    {
	//sprintf(buf, "0x%08x\n", CPC);
	buf[0]  = '\0';
	return 1;
    }
}
